package ai.platon.pulsar.e2e

import ai.platon.pulsar.WebDriverTestBase
import ai.platon.pulsar.common.AppFiles
import ai.platon.pulsar.common.AppPaths
import ai.platon.pulsar.common.emoji.PopularEmoji
import ai.platon.pulsar.common.serialize.json.prettyPulsarObjectMapper
import ai.platon.pulsar.persist.model.ActiveDOMMessage
import ai.platon.pulsar.common.printlnPro
import ai.platon.pulsar.skeleton.crawl.fetch.driver.AbstractWebDriver
import kotlinx.coroutines.delay
import org.junit.jupiter.api.Assumptions
import org.junit.jupiter.api.Disabled
import org.junit.jupiter.api.Tag
import java.io.IOException
import java.nio.file.Path
import java.text.MessageFormat
import java.util.Base64
import kotlin.test.AfterTest
import kotlin.test.BeforeTest
import kotlin.test.Ignore
import kotlin.test.Test
import kotlin.test.assertEquals
import kotlin.test.assertNotEquals
import kotlin.test.assertNotNull
import kotlin.test.assertTrue

@Tag("TimeConsumingTest")
@Tag("E2ETest")
open class PulsarWebDriverE2ETest : WebDriverTestBase() {

    private val fieldSelectors = mapOf(
        "01productTitle" to "#productTitle",
        "02acrPopover" to "#acrPopover",
        "03acrCustomerReviewText" to "#acrCustomerReviewText",
        "04productOverview" to "#productOverview_feature_div",
        "05featureBullets" to "#featurebullets_feature_div",
        "06prodDetails" to "#prodDetails",
        "07customerReviews" to "#reviewsMedley",
        "08review1" to "#cm-cr-dp-review-list div[data-hook=review]:nth-child(1)",
        "09review2" to "#cm-cr-dp-review-list div[data-hook=review]:nth-child(2)",
        "10review3" to "#cm-cr-dp-review-list div[data-hook=review]:nth-child(3)",
        "11review4" to "#cm-cr-dp-review-list div[data-hook=review]:nth-child(4)",
        "12review5" to "#cm-cr-dp-review-list div[data-hook=review]:nth-child(5)",
    )

    private val screenshotDir = AppPaths.TEST_DIR.resolve("screenshot")

    @BeforeTest
    fun setup() {
        logger.warn("Tests may fall because of page layout changing")

        session.globalCache.resetCaches()
    }

    @AfterTest
    fun tearDown() {
        session.globalCache.resetCaches()
    }

    @Test
    @Ignore("Disabled temporarily")
    fun `When navigate to a HTML page then the navigate state are correct`() = runEnhancedWebDriverTest(browser) { driver ->
        openEnhanced(e2eProductUrl, driver, 1)

        val navbarMain = driver.selectFirstTextOrNull("#navbar-main")
        val title = driver.selectFirstTextOrNull("#productTitle")
        Assumptions.assumeTrue { navbarMain != null || title != null }

        val navigateEntry = driver.navigateEntry
        assertTrue("Expect mainFrameReceived") { navigateEntry.mainFrameReceived }
        assertTrue { navigateEntry.networkRequestCount.get() > 0 }
        assertTrue { navigateEntry.networkResponseCount.get() > 0 }

        require(driver is AbstractWebDriver)
        assertEquals(200, driver.mainResponseStatus)
        assertTrue { driver.mainResponseStatus == 200 }
        assertTrue { driver.mainResponseHeaders.isNotEmpty() }
        assertEquals(200, navigateEntry.mainResponseStatus)
        assertTrue { navigateEntry.mainResponseStatus == 200 }
        assertTrue { navigateEntry.mainResponseHeaders.isNotEmpty() }
    }

    @Test
    fun `when open a HTML page then script is injected`() = runEnhancedWebDriverTest(e2eOriginUrl, browser) { driver ->
        var detail = driver.evaluateDetail("typeof(window)")
        printlnPro(detail)
        // assertNotNull(detail?.value)

        detail = driver.evaluateDetail("typeof(document)")
        printlnPro(detail)
        // assertNotNull(detail?.value)

        val r = driver.evaluate("__pulsar_utils__.add(1, 1)")
        assertEquals(2, r)

        detail = driver.evaluateDetail("JSON.stringify(__pulsar_CONFIGS)")
        val value = detail?.value?.toString()
        assertNotNull(value)
        printlnPro(value)
        assertTrue { value.contains("viewPortWidth") }

        detail = driver.evaluateDetail("JSON.stringify(__pulsar_utils__.getConfig())")
        val value2 = detail?.value?.toString()
        assertNotNull(value2)
        printlnPro(value2)
        assertTrue { value2.contains("viewPortWidth") }
    }

    @Test
    fun `open a HTML page and compute metadata`() = runEnhancedWebDriverTest(e2eOriginUrl, browser) { driver ->
        driver.evaluate("__pulsar_utils__.scrollToMiddle()")
        var detail = driver.evaluateDetail("__pulsar_utils__.compute()")
        printlnPro(detail)

        detail = driver.evaluateDetail("__pulsar_utils__.getActiveDomMessage()")
        printlnPro(detail)
        val data = detail?.value?.toString()
        assertNotNull(data)

        val message = ActiveDOMMessage.fromJson(data)
        val urls = message.urls
        assertNotNull(urls)
        assertEquals(e2eOriginUrl, urls.URL)

        val metadata = message.metadata
        assertNotNull(metadata)
        printlnPro(prettyPulsarObjectMapper().writeValueAsString(metadata))
        assertEquals(1920, metadata.viewPortWidth)
        assertEquals(1080, metadata.viewPortHeight)
        // Assumptions.assumeTrue(metadata.scrollTop > metadata.viewPortHeight)
        assertTrue { metadata.scrollTop >= 0 }
        assertTrue { metadata.scrollLeft.toInt() == 0 }
        assertTrue { metadata.clientWidth > 0 } // 1683 on my laptop
        assertTrue { metadata.clientHeight > 0 } // 986 on my laptop
    }

    @Test
    fun test_selectAttributeAll() = runEnhancedWebDriverTest(browser) { driver ->
        driver.navigateTo(e2eProductUrl)
        val navbarMain = driver.selectFirstTextOrNull("#navbar-main")
        val title = driver.selectFirstTextOrNull("#productTitle")
        Assumptions.assumeTrue { navbarMain != null || title != null }

        val selector = "body a[href]"
        driver.waitForSelector(selector)

        printlnPro("Selecting attributes: ")

        var links = driver.selectAttributeAll(selector, "href")
        assertTrue { links.isNotEmpty() }

        printlnPro("Top 10 href: ")
        links.take(10).forEach { printlnPro(it) }

        links = driver.selectAttributeAll(selector, "abs:href")
        printlnPro("NOTE: abs:href not supported by WebDriver.selectAttributeXXX()")
        printlnPro("Abs:href: ")
        links.forEach { printlnPro(it) }
        // assertTrue { links.isEmpty() }
    }

    @Test
    @Ignore("Disabled temporarily")
    fun testClickTextMatches() = runEnhancedWebDriverTest(browser) { driver ->
        openEnhanced(e2eProductUrl, driver, 1)
        val navbarMain = driver.selectFirstTextOrNull("#navbar-main")
        val title = driver.selectFirstTextOrNull("#productTitle")
        Assumptions.assumeTrue { navbarMain != null || title != null }

//        driver.waitForSelector("a[href*=stores]")
        driver.waitForSelector("a[href*=iphone]")

        // should match the anchor text "Brand: iphone"
//        driver.clickTextMatches("a[href*=stores]", "Store")
        driver.clickTextMatches("a[href*=iphone]", "iphone")
        driver.waitForNavigation()
        driver.waitForSelector("body")

        // expected url like: https://www.amazon.com/stores/Apple/page/77D9E1F7-0337-4282-9DB6-B6B8FB2DC98D?ref_=ast_bln
        val currentUrl = driver.currentUrl()
        printlnPro("The page should be redirected")
        printlnPro("Current url: $currentUrl")

        val pageSource = driver.pageSource()
        Assumptions.assumeTrue { (pageSource?.length ?: 0) > 1000 }
        Assumptions.assumeTrue { pageSource?.contains("iphone", ignoreCase = true) == true }

        assertNotEquals(e2eProductUrl, currentUrl)
        // assertContains(currentUrl, "iphone", ignoreCase = true)
    }

    @Test
    fun testClickNthAnchor() = runEnhancedWebDriverTest(mockAmazonProductUrl, browser) { driver ->
        driver.clickNthAnchor(100, "body")
//        logPrintln(href)

        driver.waitForNavigation()
        driver.waitForSelector("body")
        driver.scrollDown(5)
    }

    @Test
    fun testMouseMove() = runEnhancedWebDriverTest(mockAmazonProductUrl, browser) { driver ->
        repeat(10) { i ->
            val x = 100.0 + 2 * i
            val y = 100.0 + 3 * i

            driver.moveMouseTo(x, y)

            delay(500)
        }
    }

    @Test
    fun testMouseWheel() = runEnhancedWebDriverTest(mockAmazonProductUrl, browser) { driver ->
        driver.mouseWheelDown(5)
        val box = driver.boundingBox("body")
        printlnPro(box)
        assertNotNull(box)

        delay(3000)

        driver.mouseWheelUp(5)

        val box2 = driver.boundingBox("body")
        printlnPro(box2)
        assertNotNull(box2)
        // assertTrue { box2.height > box.height }
    }

    @Test
    fun testKeyPress() = runEnhancedWebDriverTest(browser) { driver ->
        driver.navigateTo(e2eProductUrl)
        delay(1000)

        val navbarMain = driver.selectFirstTextOrNull("#navbar-main")
        val title = driver.selectFirstTextOrNull("#productTitle")
        Assumptions.assumeTrue { navbarMain != null || title != null }

        driver.waitForSelector("#productTitle")

        assertTrue { driver.exists("#productTitle") }

        var text = driver.selectFirstTextOrNull("#productTitle")
        printlnPro("Product title: $text")

        // val selector = "#nav-search input[placeholder*=Search]"
        val selector = "form input[type=text]"
        text = driver.selectFirstAttributeOrNull(selector, "placeholder")
        printlnPro("Search bar - placeholder - 1 - driver.selectFirstAttributeOrNull() : <$text>")

        text = driver.selectFirstPropertyValueOrNull(selector, "placeholder")
        printlnPro("Search bar - placeholder - 2 - driver.selectFirstPropertyValueOrNull() : <$text>")
        assertTrue("Placeholder should not be empty") { !text.isNullOrBlank() }

        "iphone".forEach { ch ->
            driver.press(selector, "$ch")
        }
        driver.press(selector, "Digit6")
        driver.press(selector, "0")

        delay(1000)

        text = driver.selectFirstPropertyValueOrNull(selector, "value")
        printlnPro("Search bar value (should be empty) - 1: <$text>")
        assertEquals("", text)

        MessageFormat.format("{0} key pressed {0}", PopularEmoji.SPARKLES).also { printlnPro(it) }

        var evaluate = driver.evaluateDetail("document.querySelector('$selector').value")
        printlnPro("Search bar evaluate result - driver.evaluateDetail() : $evaluate")
        printlnPro("Search bar value - driver.evaluateDetail() : <${evaluate?.value}>")
        // assertEquals("Mate60", evaluate?.value)

        text = driver.selectAttributeAll(selector, "value").joinToString()
        printlnPro("Search bar value - 3 - selectAttributeAll() : <$text>")
//            assertEquals("Mate60", text)

        val html = driver.outerHTML(selector)
        printlnPro("Search bar html: >>>\n$html\n<<<")
        assertNotNull(html)
        // assertTrue { html.contains("Mate60") }

        evaluate = driver.evaluateDetail("document.querySelector('$selector').value")
        printlnPro("Search bar evaluate result - driver.evaluateDetail() : >>>\n$evaluate\n<<<")
        printlnPro("Search bar value - driver.evaluateDetail() : <${evaluate?.value}>")
        // assertEquals("Mate60", evaluate?.value)

        // TODO: FIXME: enter seems not working
        driver.press(selector, "Enter")
        driver.waitForNavigation()
        assertTrue { driver.currentUrl() != e2eProductUrl }
    }

    @Test
    fun testTypeText() = runEnhancedWebDriverTest(browser) { driver ->
        driver.navigateTo(e2eProductUrl)
        driver.waitForSelector("#productTitle")

        assertTrue { driver.exists("#productTitle") }

        var text = driver.selectFirstTextOrNull("#productTitle")
        printlnPro("Product title: $text")

        // val selector = "#nav-search input[placeholder*=Search]"
        val selector = "form input[type=text]"
        text = driver.selectFirstAttributeOrNull(selector, "placeholder")
        printlnPro("Search bar - placeholder - 1 - driver.selectFirstAttributeOrNull() : <$text>")
        assertTrue("Placeholder should not be empty") { !text.isNullOrBlank() }
        text = driver.selectAttributeAll(selector, "placeholder").joinToString()
        printlnPro("Search bar - placeholder - 2 - driver.selectAttributeAll() : <$text>")
        assertTrue("Placeholder should not be empty") { !text.isNullOrBlank() }

        text = driver.selectAttributeAll(selector, "value").joinToString()
        printlnPro("Search bar value (should be empty) - 1: <$text>")
        assertEquals("", text)

        driver.type(selector, "Mate60")

        MessageFormat.format("{0} text typed {0}", PopularEmoji.SPARKLES).also { printlnPro(it) }

        var evaluate = driver.evaluateDetail("document.querySelector('$selector').value")
        printlnPro("Search bar evaluate result - driver.evaluateDetail() : $evaluate")
        printlnPro("Search bar value - driver.evaluateDetail() : <${evaluate?.value}>")
        assertEquals("Mate60", evaluate?.value)

        text = driver.selectAttributeAll(selector, "value").joinToString()
        printlnPro("Search bar value - 3: $text")
//            assertEquals("Mate60", text)

        val html = driver.outerHTML(selector)
        printlnPro("Search bar html: $html")
        assertNotNull(html)
// assertTrue { html.contains("Mate60") }

        evaluate = driver.evaluateDetail("document.querySelector('$selector').value")
        printlnPro("Search bar evaluate result - driver.evaluateDetail() : $evaluate")
        printlnPro("Search bar value - driver.evaluateDetail() : <${evaluate?.value}>")
        assertEquals("Mate60", evaluate?.value)

        val lastUrl = driver.currentUrl()

        // TODO: FIXME: enter seems not working
        driver.press(selector, "Enter")
        driver.waitForNavigation(oldUrl = lastUrl)
        // assertTrue { driver.currentUrl() != lastUrl }
        val currentUrl = driver.currentUrl()
        Assumptions.assumingThat(currentUrl != lastUrl) {
            printlnPro("Current url: $currentUrl, last url: $lastUrl")
        }
    }

    @Test
    fun testCaptureScreenshot() = runEnhancedWebDriverTest(e2eProductUrl, browser) { driver ->
        val navbarMain = driver.selectFirstTextOrNull("#navbar-main")
        val title = driver.selectFirstTextOrNull("#productTitle")
        Assumptions.assumeTrue { navbarMain != null || title != null }

        driver.waitForSelector("#productTitle")
        assertTrue { driver.exists("body") }
        val pageSource = driver.pageSource()
        assertNotNull(pageSource)
        assertTrue { pageSource.contains(asin) }

        val paths = mutableListOf<Path>()
        fieldSelectors.entries.take(3).forEach { (name, selector) ->
            val screenshot = driver.runCatching { captureScreenshot(selector) }
                .onFailure { logger.info("Failed to captureScreenshot | $name - $selector") }
                .getOrNull()

            if (screenshot != null) {
                val path = exportScreenshot("$name.jpg", screenshot)
                paths.add(path)
                delay(1000)
            }
        }

        if (paths.isNotEmpty()) {
            printlnPro(String.format("%d screenshots are saved | %s", paths.size, paths[0].parent))
        }

        // assertTrue { paths.isNotEmpty() }
    }

    @Test
    fun testDragAndHold() = runEnhancedWebDriverTest(walmartUrl, browser) { driver ->
        // TODO: FIXME: dragAndHold not working on walmart.com
        val result = driver.evaluate("__pulsar_utils__.doForAllFrames('HOLD', 'ME')")
        printlnPro(result)
    }

    @Test
    fun `When call queryClientRects then return client rects`() = runEnhancedWebDriverTest(e2eProductUrl, browser) { driver ->
        val navbarMain = driver.selectFirstTextOrNull("#navbar-main")
        val title = driver.selectFirstTextOrNull("#productTitle")
        Assumptions.assumeTrue { navbarMain != null || title != null }

        driver.mouseWheelDown(5)
        val box = driver.boundingBox("body")
        // RectD(x=0.0, y=-600.0, width=1912.0, height=10538.828125)
        printlnPro(box)
        assertNotNull(box)

        delay(3000)

        driver.mouseWheelUp(5)

        val box2 = driver.boundingBox("body")
        // RectD(x=0.0, y=-150.0, width=1912.0, height=10538.828125)
        printlnPro(box2)
        assertNotNull(box2)

        var jsFun = "__pulsar_utils__.queryClientRects"
        var bodyInfo = driver.evaluate("$jsFun('body')")?.toString() ?: "unexpected"
        // [{0 0 1912 10538.8}, ]
        printlnPro("queryClientRects: $bodyInfo")

        jsFun = "__pulsar_utils__.queryClientRect"
        bodyInfo = driver.evaluate("$jsFun('body')")?.toString() ?: "unexpected"
        // [{0 0 1912 10538.8}, ]
        printlnPro("queryClientRect: $bodyInfo")

        jsFun = "document.body.scrollWidth"
        bodyInfo = driver.evaluate("$jsFun('body')")?.toString() ?: "unexpected"
        // [{0 0 1912 10538.8}, ]
        printlnPro("body.scrollWidth: $bodyInfo")

        jsFun = "document.body.clientWidth"
        bodyInfo = driver.evaluate("$jsFun('body')")?.toString() ?: "unexpected"
        // [{0 0 1912 10538.8}, ]
        printlnPro("body.clientWidth: $bodyInfo")
    }

    @Throws(IOException::class)
    private fun exportScreenshot(filename: String, screenshot: String): Path {
        val path = screenshotDir.resolve(filename)
        val bytes = Base64.getDecoder().decode(screenshot)
        return AppFiles.saveTo(bytes, path, true)
    }
}

